Учебный курс: Подготовка на 1С:Специалист по платформе 1С:Предприятие 8.3

Задачи оперативного учета – тема № 14:
Как разработать алгоритмы проведения документов для взаиморасчетов с использованием товарного кредита

Ранее мы уже выполнили анализ задачи, разработали учетную схему, пояснили некоторые нюансы решения, а также создали все необходимые объекты конфигурации.

В данной главе мы разработаем алгоритмы проведения документов и протестируем наше решение.

Проведение документа «Приход денег»

Процедуру ОбработкаПроведения в модуле объекта создадим и заполним вручную. Для создания процедуры можно выбрать ее имя из списка на панели инструментов.

Создание процедуры ОбработкаПроведения

Рисунок 1 – Создание процедуры ОбработкаПроведения

Готовая процедура ОбработкаПроведения будет выглядеть следующим образом:

Процедура ОбработкаПроведения(Отказ, РежимПроведения)
	// 1. Установка признака записи движений регистра Взаиморасчеты
	Движения.Взаиморасчеты.Записывать = Истина;
	
	// 2. Удаление старых движений регистра Взаиморасчеты
	Движения.Взаиморасчеты.Записать();
	
	// 3. Блокировка регистра Взаиморасчеты по контрагенту (по всем накладным)
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Взаиморасчеты");
	ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
	ЭлементБлокировки.УстановитьЗначение("Контрагент", Контрагент);
	Блокировка.Заблокировать(); 
	
	// 4. Текст запроса - получение остатков задолженности по накладным для контрагента
	// (исключая Предоплаты)
	Запрос = Новый Запрос;
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	ВзаиморасчетыОстатки.РасходнаяНакладная		КАК РасходнаяНакладная,
	|	ВзаиморасчетыОстатки.СуммаОстаток		КАК СуммаОстаток
	|ИЗ
	|	РегистрНакопления.Взаиморасчеты.Остатки(
	|		&МоментВремениДокумента,
	|		   Контрагент = &Контрагент
	|		И РасходнаяНакладная <> ЗНАЧЕНИЕ(Документ.РасходнаяНакладная.ПустаяСсылка) 
	|							//Предоплата
	|	) КАК ВзаиморасчетыОстатки
	|
	|ГДЕ
	|	ВзаиморасчетыОстатки.СуммаОстаток > 0	
	|
	|УПОРЯДОЧИТЬ ПО
	|	РасходнаяНакладная.МоментВремени";
	
	// 5. Определение момента времени для получения остатков - на момент документа
	Запрос.УстановитьПараметр("Контрагент",			Контрагент);
	Запрос.УстановитьПараметр("МоментВремениДокумента",	МоментВремени());
	РезультатЗапроса = Запрос.Выполнить();
	
	// 6. Распределение суммы из документа по долгам по накладным
	СуммаКРаспределению = СуммаПоДокументу;
	
	Если Не РезультатЗапроса.Пустой() Тогда
		
		Выборка = РезультатЗапроса.Выбрать();
		Пока Выборка.Следующий() И СуммаКРаспределению > 0 Цикл
			
			// 7. Определение суммы, которая пойдет на уменьшение долга по накладной
			СуммаКСписанию = Мин(СуммаКРаспределению, Выборка.СуммаОстаток);
			
			// 8. Уменьшение долга контрагента по накладной, по которой найден остаток
			// задолженности
			Движение = Движения.Взаиморасчеты.ДобавитьРасход();
			Движение.Период			= Дата;
			Движение.Контрагент		= Контрагент;
			Движение.РасходнаяНакладная	= Выборка.РасходнаяНакладная;
			Движение.Сумма			= СуммаКСписанию;
			
			// 9. Уменьшение распределяемой суммы платежа на сумму погашения задолженности 
			// по накладной
			СуммаКРаспределению = СуммаКРаспределению - СуммаКСписанию;
		КонецЦикла;
	КонецЕсли;
	
	// 10. Если после погашения долгов по накладным от суммы платежа что-то осталось, 
	//учитываем остаток как предоплату
	Если СуммаКРаспределению > 0 Тогда
		
		// 11. Отнесение на предоплату остатка денег после распределения по накладным
		// (увеличивает долг предприятия перед контрагентом)
		Движение = Движения.Взаиморасчеты.ДобавитьРасход();
		Движение.Период			= Дата;
		Движение.Контрагент 		= Контрагент;
		Движение.РасходнаяНакладная	= Документы.РасходнаяНакладная.ПустаяСсылка(); 
									// Предоплата
		Движение.Сумма			= СуммаКРаспределению;
	КонецЕсли; 
КонецПроцедуры

Описание основных действий

Установка признака записи движений регистра «Взаиморасчеты» (п. 1)

Т. к. документ «Приход денег» будет проведен в любом случае, можно сразу установить признак записи регистра.

Удаление движений документа (п. 2)

Выполняется удаление существующих движений документа, чтобы они не искажали реальную картину остатков на момент времени документа. Заметим, что для управляемого приложения набор движений документа будет пустым, поэтому специально очищать его нет необходимости. А существующие движения документа в базе данных будут удалены при записи такого пустого набора.

Блокировка записей регистра «Взаиморасчеты» по контрагенту из документа (п. 3)

Устанавливается блокировка записей регистра «Взаиморасчеты» по контрагенту из документа, т.е. фактически по всем накладным, связанным с контрагентом.

Алгоритм здесь реализует проведение по «старой» методике: сначала выбираются остатки, затем происходит распределение по остаткам суммы из документа. Заранее неизвестно, по каким накладным есть задолженность, т.е. какие накладные будут участвовать в движениях, поэтому блокируются все записи регистра по контрагенту.

Также необходимо обеспечить, чтобы данные по остаткам задолженностей не были изменены другими пользователями на весь период от момента получения данных об остатках задолженностей до окончания их погашения и отнесения остатка суммы платежа на предоплату. Поэтому эта блокировка накладывается еще до получения данных запросом (пп. 4, 5).

Получение данных о накладных, по которым есть задолженности (пп. 4, 5)

Запросом выбираются все накладные, по которым у контрагента есть задолженности на начало момента времени документа. Причем накладные в результате запроса будут отсортированы по возрастанию их моментов времени — в соответствии с заданным приоритетом погашения задолженностей (“начиная с самой ранней не полностью оплаченной накладной” — см. условие задачи).

Пустая ссылка на документ «Расходная накладная», которая в нашей схеме обозначает предоплату, исключена из результатов запроса, так как для дальнейшего распределения суммы платежа требуется получить только задолженности в разрезе конкретных накладных. Поэтому обработка значения NULL для выражения РасходнаяНакладная.МоментВремени не требуется: ссылка в РасходнаяНакладная всегда будет не пустой, а потому и выражение РасходнаяНакладная.МоментВремени всегда будет определено.

Условие «ВзаиморасчетыОстатки.СуммаОстаток > 0» добавлено для дополнительной устойчивости решения, на случай, если по какой-то причине остаток по накладным станет отрицательным (при нормальном ведении учета такого быть не может, отрицательные значения остатка в нашей схеме соответствуют только предоплате, но при корректировке документов задним числом появление такой ошибки не исключено).

Распределение суммы платежа по накладным с задолженностями (пп. 6, 8, 9)

Пока не закончится сумма платежа из документа, она будет распределяться по тем накладным, по которым у контрагента есть задолженности. Так как в запросе (п. 4) задано упорядочивание накладных по возрастанию их дат, погашаться задолженности по накладным будут с заданным в условии задачи приоритетом.

Отнесение остатка суммы платежа на предоплату (пп. 10, 11)

Если после погашения всех задолженностей по накладным сумма платежа контрагента полностью не израсходована, остаток этой суммы будет учтен как предоплата.

Проведение документа «Расходная накладная»

Процедуру ОбработкаПроведения в модуле объекта создадим и заполним вручную. Готовая процедура будет выглядеть следующим образом:

Процедура ОбработкаПроведения(Отказ, РежимПроведения)

	// 1. Удаление старых движений регистра Взаиморасчеты
	Движения.Взаиморасчеты.Записать();

	// 2. Блокировка регистра Взаиморасчеты по контрагенту из документа 
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.Взаиморасчеты");
	ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
	ЭлементБлокировки.УстановитьЗначение("Контрагент", Контрагент);
	Блокировка.Заблокировать(); 	
	
	// 3. Текст запроса - состояние взаиморасчетов с контрагентом 
	// и данные по кредиту контрагента 
	Запрос = Новый Запрос;
	Запрос.Текст = "
	|////////////////////////////////////////////////////////////////////////////////
	|// #0 : Результат
	|// Состояние взаиморасчетов с контрагентом
	|
	|ВЫБРАТЬ
	|	ЕСТЬNULL(СУММА(ВзаиморасчетыОстатки.СуммаОстаток), 
	|	0)				КАК СуммаОстаток,
	|
	|	ЕСТЬNULL(
	|		СУММА(
	|			ВЫБОР
	|		 		КОГДА	ВзаиморасчетыОстатки.РасходнаяНакладная 
	|					= ЗНАЧЕНИЕ(Документ.РасходнаяНакладная.ПустаяСсылка)
	|					И ВзаиморасчетыОстатки.СуммаОстаток < 0
	|
	|				ТОГДА
	|					-ВзаиморасчетыОстатки.СуммаОстаток
	|
	|				ИНАЧЕ 0
	|			КОНЕЦ	
	|		),
	|	0)				КАК СуммаПредоплаты,
	|
	|	ЕСТЬNULL(
	|		МАКСИМУМ(
	|			ВЫБОР
	|				КОГДА	ВзаиморасчетыОстатки.РасходнаяНакладная 
	|					<> ЗНАЧЕНИЕ(Документ.РасходнаяНакладная.ПустаяСсылка)
	|					И ВзаиморасчетыОстатки.СуммаОстаток > 0
	|
	|				ТОГДА 
	|					РАЗНОСТЬДАТ(ВзаиморасчетыОстатки.РасходнаяНакладная.Дата, 
	|							&ДатаДокумента, 
	|							ДЕНЬ) 
	|				ИНАЧЕ 0
	|			КОНЕЦ 
	|		),
	|	0)				КАК КоличествоДнейЗадолженности
	|
	|ИЗ
	|	РегистрНакопления.Взаиморасчеты.Остатки(&МоментВремениДокумента, 
	|						Контрагент = &Контрагент
	|	) КАК ВзаиморасчетыОстатки
	|;
	|
	|////////////////////////////////////////////////////////////////////////////////
	|// #1 : Результат
	|// Данные о разрешенном кредите для контрагента - сумма и срок	
	|
	|ВЫБРАТЬ
	|	КредитыПокупателямСрезПоследних.СуммаКредита	КАК СуммаКредита,
	|	КредитыПокупателямСрезПоследних.СрокКредита	КАК СрокКредита
	|	
	|ИЗ
	|	РегистрСведений.КредитыПокупателям.СрезПоследних(&МоментВремениДокумента,
	|							Контрагент = &Контрагент
	|	) КАК КредитыПокупателямСрезПоследних
	|";
	
	// 4.  Установка параметров запроса и выполнение пакета запросов
	Запрос.УстановитьПараметр("Контрагент",			Контрагент);
	Запрос.УстановитьПараметр("МоментВремениДокумента",	МоментВремени());
	Запрос.УстановитьПараметр("ДатаДокумента",		Дата);
	
	МассивРезультатовЗапроса = Запрос.ВыполнитьПакет();
	
	// 5. Общий баланс взаиморасчетов с учетом текущего документа
	Выборка = МассивРезультатовЗапроса[0].Выбрать();
	Выборка.Следующий();
	ОбщаяСуммаВзаиморасчетов	= Выборка.СуммаОстаток;
	СуммаПредоплаты			= Выборка.СуммаПредоплаты;
	КоличествоДнейЗадолженности	= Выборка.КоличествоДнейЗадолженности;
	
	Если ОбщаяСуммаВзаиморасчетов + СуммаПоДокументу > 0 Тогда
		// 6. У контрагента есть задолженность (с учетом данной накладной)
		// Проверим, разрешен ли ему кредит, и вписывается ли он в условия кредита
		
		// 7. Получение данных об условиях кредита
		Если МассивРезультатовЗапроса[1].Пустой() Тогда
			СуммаКредита	= 0;
			СрокКредита	= 0;
		Иначе
			Выборка = МассивРезультатовЗапроса[1].Выбрать();
			Выборка.Следующий();
			
			СуммаКредита	= Выборка.СуммаКредита;
			СрокКредита	= Выборка.СрокКредита;
		КонецЕсли;
		
		// 8. Проверки соблюдения условий кредита
		Если СуммаКредита = 0 Или СрокКредита = 0 Тогда
			// 9. Кредит не разрешен
			Сообщение = Новый СообщениеПользователю;
			Сообщение.Текст = "Сумма отгрузки превышает сумму предоплаты на "
			+ (ОбщаяСуммаВзаиморасчетов + СуммаПоДокументу)
			+ " р., а кредит контрагенту не предоставлен. Проведение отменено.";
			Сообщение.Сообщить();

			Отказ = Истина;
			
		ИначеЕсли ОбщаяСуммаВзаиморасчетов + СуммаПоДокументу > СуммаКредита Тогда
			// 10. Превышена сумма кредита
			Сообщение = Новый СообщениеПользователю;
			Сообщение.Текст = "Кредит контрагенту превышен на сумму "
			+ (ОбщаяСуммаВзаиморасчетов + СуммаПоДокументу - СуммаКредита)
			+ " р. Проведение отменено.";
			Сообщение.Сообщить();

			Отказ = Истина;
			
		ИначеЕсли КоличествоДнейЗадолженности > СрокКредита Тогда
			// 11. Превышен срок кредита
			Сообщение = Новый СообщениеПользователю;
			Сообщение.Текст = "Превышен срок кредита контрагенту на "
			+ (КоличествоДнейЗадолженности - СрокКредита)
			+ " дней. Проведение отменено.";
			Сообщение.Сообщить();

			Отказ = Истина;
		КонецЕсли;
	КонецЕсли;	
	
	Если Не Отказ Тогда
		// 12. У контрагента нет задолженности
		// или задолженность вписывается в условия кредита
		
		// 13. Установка признака записи движений регистра Взаиморасчеты
		Движения.Взаиморасчеты.Записывать = Истина;
		
		// 14. Отражение долга контрагента перед компанией по отгрузке
		// - на всю сумму накладной 
		Движение = Движения.Взаиморасчеты.ДобавитьПриход();
		
		Движение.Период			= Дата;
		Движение.Контрагент		= Контрагент;
		Движение.РасходнаяНакладная	= Ссылка;
		Движение.Сумма			= СуммаПоДокументу;
		
		// 15. Если есть предоплата, её нужно зачесть
		Если СуммаПредоплаты > 0 Тогда
			
			// 16. Зачитываем предоплату в сумме не более суммы накладной 
			СуммаКЗачету = Мин(СуммаПоДокументу, СуммаПредоплаты);
			
			// 17. Уменьшение долга контрагента по накладной сумму предоплаты 
			// (зачет предоплаты)
			Движение = Движения.Взаиморасчеты.ДобавитьРасход();
			Движение.Период			= Дата;
			Движение.Контрагент 		= Контрагент;
			Движение.РасходнаяНакладная	= Ссылка;
			Движение.Сумма			= СуммаКЗачету;			
			
			// 18. Уменьшение долга компании перед контрагентом 
			// на сумму зачтенной предоплаты
			Движение = Движения.Взаиморасчеты.ДобавитьПриход();
			Движение.Период			= Дата;
			Движение.Контрагент		= Контрагент;
			Движение.РасходнаяНакладная	= Документы.РасходнаяНакладная.ПустаяСсылка();
											// Предоплата;
			Движение.Сумма			= СуммаКЗачету;
		КонецЕсли;
	КонецЕсли;	
КонецПроцедуры

Описание основных действий

Согласно заданию документ должен отразить задолженность контрагента по отгрузке по накладной из документа, а также выполнить оплату этой накладной за счет суммы предоплаты, если она имеется. То есть основными действиями, которые нужно сделать, будут следующие:

  • Отразить задолженность контрагента по накладной на сумму отгрузки из документа
  • Проверить наличие предоплаты у контрагента
  • Если предоплата имеется, погасить задолженность контрагента по накладной не более чем на сумму отгрузки из документа и в пределах суммы предоплаты.

Кроме того, требуется проверить возможность отгрузки контрагенту в кредит. Для этого потребуется выполнить следующие действия:

  • Определить, разрешен ли контрагенту кредит на момент документа, и если разрешен, то каковы его условия (размер и срок кредита)
  • Определить остаток разрешенного размера (суммы) кредита
  • Определить срок фактического использования кредита
  • Проверить выполнение условия: сумма отгрузки контрагенту не должна превышать сумму предоплаты, если кредит контрагенту не предоставлен. Если же кредит предоставлен, сумма отгрузки, уменьшенная на сумму предоплаты, не должна превышать остатка разрешенного размера кредита, а срок фактического использования кредита должен быть не более разрешенного. Проводить документ можно только в том случае, если эти условия выполнены. В противном случае проведение документа должно быть отменено с выдачей пользователю диагностического сообщения.

Порядок действий следующий.

Удаление движений документа (п. 1)

Выполняется удаление существующих движений документа, чтобы они не искажали реальную картину остатков на момент времени документа.

Блокировка записей регистра «Взаиморасчеты» по контрагенту из документа (п. 2)

Устанавливается блокировка записей регистра «Взаиморасчеты» по контрагенту из документа,  т.е. фактически по всем накладным и предоплатам, связанным с контрагентом.

Алгоритм здесь реализует проведение по «старой» методике: сначала выбираются остатки, затем происходит их анализ и формирование движений на основе полученных данных. Заранее неизвестно, по каким именно накладным есть задолженность (от состава накладных в задолженности зависит вычисляемое значение фактического срока кредита: он рассчитывается от самой ранней не полностью оплаченной накладной).

Кроме того, изменение состава накладных в задолженности (параллельно работающими пользователями, например, при разнесении оплаты) может привести и к изменению значения фактического срока кредита. Поэтому приходится блокировать все записи регистра по контрагенту в целом — по всем накладным. Установить в блокировке отбор по какому-то множеству накладных, чтобы сократить количество блокируемых записей, тут нельзя.

Блокировкой необходимо обеспечить, чтобы данные по остаткам взаиморасчетов не были изменены другими пользователями на период от момента получения данных об остатках взаиморасчетов до окончания формирования движений по документу. То есть пока формируются движения текущего документа, используемые документом данные (размер предоплаты, остаток разрешенного размера кредита, самая ранняя не полностью оплаченная накладная, по которой вычисляется фактический срок предоставленного кредита) не должны быть изменены другими пользователями. Поэтому эта блокировка устанавливается еще до получения данных запросом (пп. 3, 4) и фактически длится до окончания транзакции обработки проведения.

Выполнение запроса для получения данных о состоянии взаиморасчетов, о предоплате, условиях кредита и фактическом использовании кредита (пп. 3, 4)

Запросом выбираются (на момент документа) следующие данные:

  • Общий остаток задолженности контрагента с учетом предоплаты
  • Сумма предоплаты
  • Количество дней задолженности контрагента, начиная с самой ранней не полностью оплаченной накладной
  • Условия разрешенного контрагенту кредита (размер кредита и его срок).

Условия «ВзаиморасчетыОстатки.СуммаОстаток < 0» для предоплаты и «ВзаиморасчетыОстатки.СуммаОстаток > 0» для задолженностей по накладным при вычислении фактического срока использования кредита добавлены для дополнительной устойчивости решения на случай, если по какой-то причине остаток по предоплате станет положительным либо остаток по накладным станет отрицательным. (При нормальном ведении учета такого быть не может, отрицательные значения остатка в нашей схеме соответствуют только предоплате, а положительные значения остатка — только долгам по накладным. Однако при корректировке документов задним числом появление таких ошибок полностью исключить нельзя).

В данном случае выполняются 2 отдельных запроса в пакете запросов. В результате будет возвращен массив результатов запросов, каждый из которых будет обработан отдельно.

Заметим, что в тексте запроса можно писать комментарии. Как и в программном коде, они отделяются от основного текста символами «//». Полезно, особенно если текст запроса большой и представляет собой пакет отдельных запросов, указывать номер запроса в пакете, а также делать небольшие пометки, что именно происходит в запросе.

Получение данных о состоянии взаиморасчетов, о предоплате и фактическом использовании кредита (п. 5)

Из результатов первого (с индексом 0) запроса пакета запросов получаем следующие данные:

  • Общий остаток задолженности контрагента, с учетом предоплаты
  • Сумму предоплаты
  • Количество дней задолженности контрагента, начиная с самой ранней не полностью оплаченной накладной.

Заметим, что в тексте запроса использованы агрегатные функции без полей группировки, поэтому данные в результате запроса будут всегда. По этой причине результат запроса не проверяется на пустоту и отсутствует проверка результата функции Выборка.Следующий().

Поясним, почему в первый запрос пакета (с индексом 0) добавлена обработка значения NULL.

Использование агрегатных функций без явного указания полей группировки приводит к расчету итоговых значений по всем вошедшим в результат записям. На первый взгляд, если источник данных пуст, то и результат запроса будет пустым. Однако при группировке по запросу в целом (без явного указания полей группировки) запрос всегда будет содержать данные, даже если данные в источнике отсутствуют. В этом случае в результате запроса будет одна строка, все значения полей в которой равны NULL.

В нашей задаче если данных об остатках взаиморасчетов нет, то все вычисляемые на их основе агрегатные функции вернут значение NULL. Именно поэтому в запросе и потребовалась обработка значения NULL.

Ещё одна особенность связана с использованием виртуальной таблицы Остатки. В нашем запросе для виртуальной таблицы Остатки регистра «Взаиморасчеты» фактически задано получение остатков регистра с детализацией до каждой накладной (значение измерения РасходнаяНакладная регистра использовано в операторах ВЫБОР КОГДА…). Если данных в регистре нет, виртуальная таблица Остатки будет пуста. А так как задана детализация по накладным, поля запроса с остатками и накладными должны быть чем-то заполнены, но заполнить их нечем (нет данных). Поэтому им присваивается значение NULL. А это в свою очередь приводит к тому, что результатами вычисления агрегатных функций также будет NULL.

В нашем случае возможные значения NULL обработаны, поэтому в качестве значений полей запроса будут всегда числовые значения.

Для сравнения приведем несколько похожих конструкций, приводящих однако к другим результатам и требующих другой обработки.

Если бы в запросе не было детализации по накладным, т.е. в запросе было бы определено только поле ВзаиморасчетыОстатки.СуммаОстаток, то даже в случае отсутствия данных в регистре «Взаиморасчеты» виртуальная таблица Остатки содержала бы одну строку со значением поля СуммаОстаток, равным нулю. В этом случае значения агрегатных функций были бы определены (не равны NULL) и обработка значения NULL не понадобилась бы.

Пример такого запроса:

ВЫБРАТЬ
  СУММА(ВзаиморасчетыОстатки.СуммаОстаток КАК СуммаОстаток
ИЗ
  РегистрНакопления.Взаиморасчеты.Остатки(&МоментВремениДокумента, Контрагент = &Контрагент
  ) КАК ВзаиморасчетыОстатки

Если же в запросе группировка велась бы не в целом по запросу, а по отдельным полям, т.е. в запросе были бы явно определены поля группировки (например, в нашем примере была бы явно указана группировка по полю Контрагент), то результат был бы иным. В этом случае при отсутствии данных в регистре «Взаиморасчеты» результат запроса был бы пустым, группировка по значению NULL (значению поля Контрагент) не дала бы результата. При этом значения NULL в результате запроса не появились бы и их обработка также не понадобилась бы. Но пришлось бы изменить обработку результата запроса, чтобы учесть, что он может быть пустым.

Пример такого запроса:

ВЫБРАТЬ
	ВзаиморасчетыОстатки.Контрагент			КАК Контрагент,
	СУММА(ВзаиморасчетыОстатки.СуммаОстаток)	КАК СуммаОстаток,
	СУММА(
		ВЫБОР
			КОГДА	ВзаиморасчетыОстатки.РасходнаяНакладная 
					= ЗНАЧЕНИЕ(Документ.РасходнаяНакладная.ПустаяСсылка)
				И ВзаиморасчетыОстатки.СуммаОстаток < 0
			ТОГДА -ВзаиморасчетыОстатки.СуммаОстаток
			ИНАЧЕ 0
		КОНЕЦ		
	) 						КАК СуммаПредоплаты
ИЗ
	РегистрНакопления.Взаиморасчеты.Остатки(&МоментВремениДокумента, Контрагент = &Контрагент
	) КАК ВзаиморасчетыОстатки
СГРУППИРОВАТЬ ПО
	ВзаиморасчетыОстатки.Контрагент
Получение данных об условиях кредита и проверка возможности отгрузки в кредит, если сумма отгрузки превышает сумму предоплаты (пп. 6 – 11)

Если с учетом суммы отгрузки по текущей накладной остаток взаиморасчетов положительный, значит, даже после зачета предоплаты контрагент останется должен компании. А значит, отгрузку ему можно производить только в том случае, если ему разрешен кредит.

В этом случае из результата второго запроса (с индексом 1) выбираются данные о сумме и сроке предоставленного контрагенту кредита (п. 7) и выполняется проверка соблюдения условий кредита (п. 8).

Если сумма или срок кредита не заданы, то считаем, что предоставление кредита контрагенту не разрешено, поэтому отгрузка контрагенту запрещена (п. 9).

Если же предоставление кредита контрагенту разрешено, но общая сумма задолженности с учетом суммы отгрузки по данной накладной превышает разрешенный размер кредита, то отгрузка контрагенту также запрещается (п. 10).

Если же и кредит контрагенту разрешен, и общая сумма задолженности с учетом суммы отгрузки по данной накладной не превышает разрешенный размер кредита, но фактический срок использования кредита превышает разрешенный, то отгрузка контрагенту также запрещается (п. 11).

Проверки на возможность отгрузки успешно пройдены (п. 12)

Если предыдущие проверки пройдены, т.е. они не выставили параметр Отказ в значение Истина, то нужно завершить проведение документа выполнением заданных движений.

Установка признака записи движений регистра «Взаиморасчеты» (п. 13)

Т. к. документ будет проведен, нужно, чтобы его движения были записаны по окончании процесса проведения, поэтому устанавливаем признак записи регистра «Взаиморасчеты». До успешного выполнения проверок устанавливать этот признак не имело смысла, т.к. не было известно, будет ли выполнено проведение и сформированы движения по регистру.

Установка признака записи движений регистра «Взаиморасчеты» (п. 14)

Отражаем в движениях сумму отгрузки товаров контрагенту — на всю сумму накладной.

Если у контрагента имеется предоплата, производим погашение задолженности контрагента за счет предоплаты (пп. 15-18)

Сумму предоплаты, если она у контрагента имеется, расходуем на погашение задолженности по текущей накладной. В качестве суммы зачета предоплаты используется минимальное из значений предоплаты и суммы задолженности по накладной.

Казалось бы, этот код можно оптимизировать, если сразу записывать в качестве задолженности по отгрузке сумму документа, уменьшенную на сумму предоплаты. В этом случае было бы не 3, а только 2 движения: увеличение задолженности контрагента по накладной на значение (СуммаПоДокументу – СуммаКЗачету) и уменьшение предоплаты на значение СуммаКЗачету.

Технически состояние расчетов после произведенных операци7й было бы идентичным. Но вот с точки зрения отражения произведенных хозяйственных операций сделанные движения выглядели бы некорректно. Ведь в данном документе две логические операции: отгрузка и оплата за счет предоплаты. То есть используя 3 движения, мы отражаем обороты более правильно с точки зрения произведенных хозяйственных операций. Поэтому мы и реализуем эти операции именно в таком виде, с кажущейся избыточностью.

Тестовый пример

Предположим, что на 01.01.2018 взаиморасчеты с покупателем отсутствовали. Данных о предоставленном кредите также не имелось.

Попробуем выполнить отгрузку контрагенту:

Тестовый документ «Расходная накладная»

Рисунок 2 – Тестовый документ «Расходная накладная»

Документ не проводится с выдачей сообщения:

Диагностическое сообщение при проведении документа «Расходная накладная»

Рисунок 3 – Диагностическое сообщение при проведении документа «Расходная накладная»

Внесем сумму предоплаты в размере 3000 р. за день до даты накладной:

Тестовый документ «Приход денег»

Рисунок 4 – Тестовый документ «Приход денег»

и снова попробуем провести расходную накладную. Документ не проводится с выдачей сообщения:

Диагностическое сообщение при проведении документа «Расходная накладная»

Рисунок 5 – Диагностическое сообщение при проведении документа «Расходная накладная»

Увеличим сумму предоплаты от 09.01.2018 до 6000 р., после чего попробуем провести расходную накладную ещё раз. Теперь расходная накладная проведена успешно.

Убедимся, что движения документов выполнены правильно: выполним в консоли запросов следующий запрос:

ВЫБРАТЬ
	ВзаиморасчетыОстаткиИОбороты.Контрагент			КАК Контрагент,
	ВзаиморасчетыОстаткиИОбороты.РасходнаяНакладная		КАК Накладная,
	ВзаиморасчетыОстаткиИОбороты.Регистратор		КАК Регистратор,
	ВзаиморасчетыОстаткиИОбороты.СуммаНачальныйОстаток	КАК НачОст,
	ВзаиморасчетыОстаткиИОбороты.СуммаПриход		КАК Приход,
	ВзаиморасчетыОстаткиИОбороты.СуммаРасход		КАК Расход,
	ВзаиморасчетыОстаткиИОбороты.СуммаКонечныйОстаток	КАК КонОст
ИЗ
	РегистрНакопления.Взаиморасчеты.ОстаткиИОбороты(,,Авто) 
	КАК ВзаиморасчетыОстаткиИОбороты
УПОРЯДОЧИТЬ ПО 
	ВзаиморасчетыОстаткиИОбороты.ПериодСекунда, 
	ВзаиморасчетыОстаткиИОбороты.НомерСтроки

Результат запроса:

Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Рисунок 6 – Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Теперь внесем для контрагента данные о предоставленном ему кредите:

Внесение данных о кредите для покупателя

Рисунок 7 – Внесение данных о кредите для покупателя

Создадим ещё одну накладную от 16. 01.2018 на сумму 10000 р.:

Тестовый документ «Расходная накладная»

Рисунок 8 – Тестовый документ «Расходная накладная»

Накладная успешно проведена. В данном случае сумма отгрузки вписывается в действующие для  покупателя условия кредита.

Увеличим сумму накладной до 11000 р. В этом случае накладная также успешно проводится. Помимо кредита учтен также и остаток предоплаты в 1000 р.

Увеличим сумму накладной до 12000 р. В этом случае накладная не проводится с выдачей сообщения:

Диагностическое сообщение при проведении документа «Расходная накладная»

Рисунок 9 – Диагностическое сообщение при проведении документа «Расходная накладная»

Уменьшим сумму накладной до 5000 р. В этом случае накладная успешно проводится. По логике данной задачи при проведении накладной должна была произойти оплата долга по отгрузке за счет предоплаты на сумму 1000 р. и ещё 4000 р. — это отгрузка в кредит. Убедимся в этом, выполнив запрос по остаткам и оборотам регистра «Взаиморасчеты»:

Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Рисунок 10 – Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Создадим ещё 3 расходных накладных: от 17.01.2018, 18.01.2018 и 19.01.2018 на суммы 2000 р., 3000 р. и 1000 р. Эти накладные также успешно проведены, т.к. они вписываются в условия кредита.

Состояние взаиморасчетов теперь следующее:

Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Рисунок 11 – Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Видим, что сумма кредита (10000 р.) использована покупателем полностью.

Установим сумму накладной от 19.01.2018 в 2000 р. Накладная не проводится с выдачей сообщения:

Диагностическое сообщение при проведении документа «Расходная накладная»

Рисунок 12 – Диагностическое сообщение при проведении документа «Расходная накладная»

Установим сумму накладной от 19.01.2018 в 1000 р. Накладная успешно проведена.

Теперь изменим дату этой накладной с 19.01.2018 на 22.01.2018. Накладная не проводится с выдачей сообщения:

Диагностическое сообщение при проведении документа «Расходная накладная»

Рисунок 13 – Диагностическое сообщение при проведении документа «Расходная накладная»

Действительно, срок кредита, предоставленный покупателю, — 5 дней. Самая ранняя неоплаченная накладная — от 16.01.2018 на сумму 4000 р. Срок её оплаты: 16.01.2018 + 5 дней = 21.01.2018. 22.01.2018 по этой накладной будет просрочка в 1 день.

Изменим дату этой накладной на 21.01.2018. Накладная успешно проведена.

Теперь проверим, как сработает распределение суммы оплаты по задолженностям по накладным.

Создадим документ «Приход денег» на сумму 7000 р. от 23.01.2018. После проведения документа состояние взаиморасчетов будет следующим:

Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Рисунок 14 – Результат выполнения запроса по остаткам и оборотам регистра «Взаиморасчеты»

Видим, что документ «Приход денег» распределил сумму оплаты по накладным, начиная с самых ранних:
4000 р. из 4000 р. долга ушло на накладную от 16.01.2018,
2000 р. из 2000 р. долга ушло на накладную от 17.01.2018,
1000 р. из 3000 р. долга ушло на накладную от 18.01.2018.
Таким образом, у покупателя остался долг в 2000 р. по накладной от 18.01.2018 и 1000 р. по накладной от 21.01.2018.
Общая задолженность равна 3000 р. Остатки взаиморасчетов на конец дня 23.01.2018 имеют вид:

Результат выполнения запроса по остаткам регистра «Взаиморасчеты»

Рисунок 15 – Результат выполнения запроса по остаткам регистра «Взаиморасчеты»

Изменим с 24.01.2018 условия кредита для покупателя: срок кредита увеличим до 7 дней, а сумму кредита уменьшим до 5000 р.:

Изменение условий кредита для покупателя

Рисунок 16 – Изменение условий кредита для покупателя

Создадим ещё одну расходную накладную от 25.01.2018 на сумму 2000 р. Накладная успешно проведена. Покупатель прошел по новым условиям кредита: срок до 25.01.2018 (18.01.2018 + 7 дней) и сумма до 5000 р. (3000 р. — текущая задолженность, 2000 р. — сумма накладной).

Увеличим сумму накладной до 3000 р. Накладная не проводится с выдачей сообщения:

Диагностическое сообщение при проведении документа «Расходная накладная»

Рисунок 17 – Диагностическое сообщение при проведении документа «Расходная накладная»

Вернув сумму накладной, равную 2000 р., изменим дату накладной на 26.01.2018. Накладная не проводится с выдачей сообщения:

Диагностическое сообщение при проведении документа «Расходная накладная»

Рисунок 18 – Диагностическое сообщение при проведении документа «Расходная накладная»

Как видим, контроль по новым условиям кредита также работает корректно.

Таким образом, по результатам тестирования можно сделать вывод, что заданная в условии задачи схема работы реализована правильно.

Заключение

На этом решение задачи завершено. Приведенный вариант решения интересен рядом особенностей:

  • Отрицательные остатки регистра не являются ошибкой, а являются частью решения
  • В качестве специального значения для обозначения предоплат использована пустая ссылка на документ «Расходная накладная»
  • Контроль отрицательных остатков регистра в привычном смысле тут отсутствует. Для проверки возможности проведения документа выполняется расчет контролируемых значений по данным из нескольких регистров информационной базы.

Комментарии закрыты